﻿/// <reference path="Interpolaters.js" />
/*
	Name:           Worley Noise
	Description:
					This library provides the implementation
					of Worley Noise using the original source by 
					Steven Worley found on  http://carljohanrosen.com/processing/index.php?page=oc&topic=5.3
	Date: 2014/02/07
	Version: 0.0.1
	Author: Dave Russell 
*/
WorleyNoise = new function () {
    this.distanceArray = new Array(3);
    this.CombineDistanceFunction = null;
    this.DistanceFunc = null;
    this.Seed = 3221;

    var OFFSET_BASIS = 2166136261;
    var FNV_PRIME = 16777619;

    this.CombineFunction1 = function (arr) {
        return arr[0];
    };

    this.CombineFunction2 = function (arr) {
        return arr[1] - arr[0];
    };
    
    this.CombineFunction3 = function (arr) {
        return arr[2] - arr[0];
    };

    this.Get1D = function (x) {
        return this.noise({ x: x, y: 0, z: 0 });
    };

    this.Get2D = function (x, y) {
        return this.noise({ x: x, y: y, z: 0 });
    }    

    this.Get3D = function (x, y, z) {
        return this.noise({ x: x, y: y, z: z });
    }

    this.noise = function (input) {
        var value = 0;

        var lastRandom;
        var numberFeaturePoints;
        var randomDiff = { x: 0, y: 0, z: 0 };
        var featurePoint = { x: 0, y: 0, z: 0 };

        var cubeX, cubeY, cubeZ;

        for (var i = 0; i < this.distanceArray.length; i++) {
            this.distanceArray[i] = 6666;
        }

        var evalCubeX = parseInt(Math.floor(input.x));
        var evalCubeY = parseInt(Math.floor(input.y));
        var evalCubeZ = parseInt(Math.floor(input.z));

        for (var i = -1; i < 2; ++i)
            for (var j = -1; j < 2; ++j)
                for (var k = -1; k < 2; ++k) {
                    cubeX = evalCubeX + i;
                    cubeY = evalCubeY + j;
                    cubeZ = evalCubeZ + k;

                    //2. Generate a reproducible random number generator for the cube
                    lastRandom = this.lcgRandom(this.hash((cubeX + this.Seed) & 0xffffffff, (cubeY) & 0xffffffff, (cubeZ) & 0xffffffff));
                    //3. Determine how many feature points are in the cube
                    numberFeaturePoints = this.probLookup(lastRandom);
                    //4. Randomly place the feature points in the cube
                    for (var l = 0; l < numberFeaturePoints; ++l) {
                        lastRandom = this.lcgRandom(lastRandom);
                        randomDiff.X = lastRandom / 0x100000000;

                        lastRandom = this.lcgRandom(lastRandom);
                        randomDiff.Y = lastRandom / 0x100000000;

                        lastRandom = this.lcgRandom(lastRandom);
                        randomDiff.Z = lastRandom / 0x100000000;

                        featurePoint
                            = { x: randomDiff.X + cubeX, y: randomDiff.Y + cubeY, z: randomDiff.Z + cubeZ };

                        //5. Find the feature point closest to the evaluation point. 
                        //This is done by inserting the distances to the feature points into a sorted list
                        this.insert(this.distanceArray, this.DistanceFunc(input, featurePoint));
                    }
                    //6. Check the neighboring cubes to ensure their are no closer evaluation points.
                    // This is done by repeating steps 1 through 5 above for each neighboring cube
                }

        var color = this.CombineDistanceFunction(this.distanceArray);
        if (color < 0) color = 0;
        if (color > 1) color = 1;

        return color;
    };
    
    this.EuclidianDistanceFunc = function (p1, p2) {
        return (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)
            + (p1.z - p2.z) * (p1.z - p2.z);
    };

    this.ManhattanDistanceFunc = function (p1, p2) {
        return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y) + Math.abs(p1.z - p2.z);
    };

    this.ChebyshevDistanceFunc = function (p1, p2) {
        var diff = { x: p1.x - p2.x, y: p1.y - p2.y,z: p1.z - p2.z};
        return Math.max(Math.max(Math.abs(diff.x), Math.abs(diff.y)), Math.abs(diff.z));
    };

    this.probLookup = function (value) {
        value = value & 0xffffffff;
        if (value < 393325350) return 1 & 0xffffffff;
        if (value < 1022645910) return 2 & 0xffffffff;
        if (value < 1861739990) return 3 & 0xffffffff;
        if (value < 2700834071) return 4 & 0xffffffff;
        if (value < 3372109335) return 5 & 0xffffffff;
        if (value < 3819626178) return 6 & 0xffffffff;
        if (value < 4075350088) return 7 & 0xffffffff;
        if (value < 4203212043) return 8 & 0xffffffff;
        return 9 & 0xffffffff;
    };

    this.insert = function (arr, value) {
        var temp;
        for (var i = arr.length - 1; i >= 0; i--) {
            if (value > arr[i]) break;
            temp = arr[i];
            arr[i] = value;
            if (i + 1 < arr.length) arr[i + 1] = temp;
        }
    };

    this.lcgRandom = function (lastValue) {
        return (((1103515245 & 0xffffffff) * lastValue + (12345 & 0xffffffff)) % 0x100000000) & 0xffffffff;
    };


    this.hash = function (i, j, k) {
        return ((((((OFFSET_BASIS ^ (i & 0xffffffff)) * FNV_PRIME) ^ (j & 0xffffffff)) * FNV_PRIME)
            ^ (k & 0xffffffff)) * FNV_PRIME) & 0xffffffff;
    };
};